/*
 * Copyright (c) 2022, Arm Limited and Contributors. All rights reserved.
 * SPDX-License-Identifier: Apache-2.0
 */

#include "lwip_stack.h"

#include "cmsis_os2.h"
#include "ethernetif.h"
#include "lwip/dhcp.h"
#include "lwip/err.h"
#include "lwip/netif.h"
#include "lwip/tcpip.h"
#include "lwip_cmsis_netif.h"
#include "lwipopts.h"

#include <stdbool.h>
#include <string.h>

#include "Driver_ETH_MAC.h"
#include "ethif_config.h"

/* Ethernet MAC Driver */
extern ARM_DRIVER_ETH_MAC ARM_Driver_ETH_MAC_(ETH_DRV_NUM);

/* secure sockets requires errno to communicate error codes */
int errno = 0;

static netif_context_t lwip_context = {0};

#if LWIP_IPV4
static const ip_addr_t *get_ipv4_addr(const struct netif *netif)
{
    if (!netif_is_up(netif)) {
        return NULL;
    }

    if (!ip4_addr_isany(netif_ip4_addr(netif))) {
        return netif_ip_addr4(netif);
    }
    return NULL;
}
#endif

#if LWIP_IPV6
static const ip_addr_t *get_ipv6_addr(const struct netif *netif)
{
    if (!netif_is_up(netif)) {
        return NULL;
    }

    for (int i = 0; i < LWIP_IPV6_NUM_ADDRESSES; i++) {
        if (ip6_addr_isvalid(netif_ip6_addr_state(netif, i)) && !ip6_addr_islinklocal(netif_ip6_addr(netif, i))) {
            return netif_ip_addr6(netif, i);
        }
    }

    for (int i = 0; i < LWIP_IPV6_NUM_ADDRESSES; i++) {
        if (ip6_addr_isvalid(netif_ip6_addr_state(netif, i))) {
            return netif_ip_addr6(netif, i);
        }
    }
    return NULL;
}
#endif

static const ip_addr_t *get_ip_addr(bool any_addr, const struct netif *netif)
{
    const ip_addr_t *pref_ip_addr = 0;

#if LWIP_IPV4 && LWIP_IPV6
    const ip_addr_t *npref_ip_addr = 0;
#if IP_VERSION_PREF == PREF_IPV4
    pref_ip_addr = get_ipv4_addr(netif);
    npref_ip_addr = get_ipv6_addr(netif);
#else
    pref_ip_addr = get_ipv6_addr(netif);
    npref_ip_addr = get_ipv4_addr(netif);
#endif
#elif LWIP_IPV6
    pref_ip_addr = get_ipv6_addr(netif);
#elif LWIP_IPV4
    pref_ip_addr = get_ipv4_addr(netif);
#else
#error At least one of LWIP_IPV6 and LWIP_IPV4 is required
#endif

    if (pref_ip_addr) {
        return pref_ip_addr;
    }
#if LWIP_IPV4 && LWIP_IPV6
    else if (npref_ip_addr && any_addr) {
        return npref_ip_addr;
    }
#endif

    return NULL;
}

static void copy_lwip_ip(ip_address_t *dst, const ip_addr_t *src)
{
#if LWIP_IPV4 && LWIP_IPV6
    switch (src->type) {
        case IPADDR_TYPE_V4:
            dst->type = IP_V4_ADDR;
            dst->addr.ipv4 = src->u_addr.ip4.addr;
            break;
        /* For IPADDR_TYPE_ANY ("dual-stack") lwIP initialises the ipv6 memory
         * with zeros (see IPADDR_ANY_TYPE_INIT in lwip/ip_addr.h)
         */
        case IPADDR_TYPE_V6:
        case IPADDR_TYPE_ANY:
            dst->type = IP_V6_ADDR;
            memcpy(dst->addr.ipv6, src->u_addr.ip6.addr, sizeof(dst->addr.ipv6));
            break;
        default:
            LWIP_PLATFORM_ASSERT("Unreachable");
            break;
    }
#elif LWIP_IPV6
    dst->type = IP_V6_ADDR;
    memcpy(dst->addr.ipv6, src->addr, sizeof(dst->addr.ipv6));
#elif LWIP_IPV4
    dst->type = IP_V4_ADDR;
    dst->addr.ipv4 = src->addr;
#else
#error At least one of LWIP_IPV6 and LWIP_IPV4 is required
#endif
}

static void dispatch_network_event(netif_context_t *context, network_state_callback_event_t event)
{
    network_state_event_args_t event_args = {.event = event};
    switch (event) {
        case NETWORK_UP:
            copy_lwip_ip(&(event_args.up_event_args.ip), get_ip_addr(true, &context->lwip_netif));
            break;
        case NETWORK_DOWN:
            /* Do nothing. */
            break;
        default:
            LWIP_PLATFORM_ASSERT("Unreachable");
            break;
    }

    context->network_state_callback(&event_args);
}

static void netif_status_callback(struct netif *netif)
{
    if (lwip_context.network_state_callback) {
        if (netif_is_up(netif) && netif_is_link_up(netif)) {
            // Check if IP address is available
            if (get_ip_addr(true, netif)) {
                dispatch_network_event(&lwip_context, NETWORK_UP);
            }
        } else if (!netif_is_up(netif) && netif_is_link_up(netif)) {
            dispatch_network_event(&lwip_context, NETWORK_DOWN);
        }
    }
}

static err_t ethernet_interface_init(netif_context_t *context)
{
#if LWIP_IPV4
    const ip4_addr_t ipaddr = {PP_HTONL(LWIP_IPV4_STATIC_ADDRESS_IP_ADDRESS)};
    const ip4_addr_t netmask = {PP_HTONL(LWIP_IPV4_STATIC_ADDRESS_IP_NETMASK)};
    const ip4_addr_t gw = {PP_HTONL(LWIP_IPV4_DEFAULT_GATEWAY)};
#endif

#if !LWIP_SET_DEFAULT_NETIF_HW_ADDRESS
    /* Note: ethernetif_init overwrites the default HW MAC address.
     * We read it here and reset it to the default after ethernetif_init. */
    ARM_ETH_MAC_ADDR default_hw_mac_address;
    ARM_Driver_ETH_MAC_(ETH_DRV_NUM).Initialize(NULL);
    ARM_Driver_ETH_MAC_(ETH_DRV_NUM).PowerControl(ARM_POWER_FULL);
    ARM_Driver_ETH_MAC_(ETH_DRV_NUM).GetMacAddress(&default_hw_mac_address);
    ARM_Driver_ETH_MAC_(ETH_DRV_NUM).Uninitialize();
#endif
    struct netif *n = netif_add(&context->lwip_netif,
#if LWIP_IPV4
                                &ipaddr,
                                &netmask,
                                &gw,
#endif
                                0 /* state */,
                                ethernetif_init,
                                tcpip_input);

    if (!n) {
        return ERR_IF;
    }

#if LWIP_SET_DEFAULT_NETIF_HW_ADDRESS
    context->lwip_netif.hwaddr[0] = ((LWIP_DEFAULT_NETIF_HW_ADDRESS >> 40) & 0xFF);
    context->lwip_netif.hwaddr[1] = ((LWIP_DEFAULT_NETIF_HW_ADDRESS >> 32) & 0xFF);
    context->lwip_netif.hwaddr[2] = ((LWIP_DEFAULT_NETIF_HW_ADDRESS >> 24) & 0xFF);
    context->lwip_netif.hwaddr[3] = ((LWIP_DEFAULT_NETIF_HW_ADDRESS >> 16) & 0xFF);
    context->lwip_netif.hwaddr[4] = ((LWIP_DEFAULT_NETIF_HW_ADDRESS >> 8) & 0xFF);
    context->lwip_netif.hwaddr[5] = ((LWIP_DEFAULT_NETIF_HW_ADDRESS >> 0) & 0xFF);
    ARM_Driver_ETH_MAC_(ETH_DRV_NUM).SetMacAddress((ARM_ETH_MAC_ADDR *)&context->lwip_netif.hwaddr);
#else
    ARM_Driver_ETH_MAC_(ETH_DRV_NUM).SetMacAddress(&default_hw_mac_address);
    ARM_Driver_ETH_MAC_(ETH_DRV_NUM).GetMacAddress((ARM_ETH_MAC_ADDR *)&context->lwip_netif.hwaddr);
#endif // LWIP_SET_DEFAULT_NETIF_HW_ADDRESS

    netif_set_status_callback(&context->lwip_netif, netif_status_callback);
    netif_set_link_callback(&context->lwip_netif, netif_status_callback);

    netif_set_default(&context->lwip_netif);
    netif_set_up(&context->lwip_netif);

#if LWIP_DHCP
    dhcp_start(&context->lwip_netif);
#endif
    netif_create_ip6_linklocal_address(&context->lwip_netif, (u8_t)(context->lwip_netif.hwaddr_len == ETH_HWADDR_LEN));
    return ERR_OK;
}

static void tcpip_init_done(void *arg)
{
    sys_sem_signal(arg);
}

static err_t lwip_network_init()
{
    sys_sem_t tcpip_done;
    if (ERR_OK != sys_sem_new(&tcpip_done, 0)) {
        LWIP_PLATFORM_ASSERT("Cannot create semaphore");
        return ERR_IF;
    }

    /* Initialise LwIP, providing the callback function and callback semaphore */
    tcpip_init(tcpip_init_done, &tcpip_done);

    sys_arch_sem_wait(&tcpip_done, 0);
    sys_sem_free(&tcpip_done);

    return ERR_OK;
}

void lwip_task(void *network_state_callback)
{
    err_t ret;
    lwip_context.network_state_callback = (network_state_callback_t)(network_state_callback);

    ret = lwip_network_init();
    LWIP_ASSERT("Lwip network init failed", (ret == ERR_OK));

    ethernet_interface_init(&lwip_context);

    /* this never exits and runs the lwip network stack on this thread */
    while (1) {
        /* handle input */
        ethernetif_check_link(&lwip_context.lwip_netif);
        ethernetif_poll(&lwip_context.lwip_netif);
        sys_check_timeouts();
        /* TODO: The Ethernetif CMSIS component currently only supports polling.
         *       Until semaphore-based version is available, this thread mustn't
         *       block, as it has above normal priority, hence the need to yield
         *       with osDelay */
        osDelay(10);
    }
}
